const http = require("http");
const fs = require("fs/promises");
const querystring = require("querystring");
const url = require("url");
const path = require("path");
const os = require("os");
const chalk = require("chalk");
const ejs = require("ejs");
const mime = require("mime");
const crypto = require("crypto");
const zlib = require("zlib"); // crypto 做加密   zlib 压缩

const { readFileSync, createReadStream, statSync, existsSync } = require("fs");

// 在老版的服务端中，引入模板引擎来实现页面的渲染 ejs handlebar nunjucks jade underscore
class Server {
  constructor(options = {}) {
    this.port = options.port;
    this.dir = options.dir;
    this.hosts = Object.values(os.networkInterfaces())
      .flat()
      .filter((item) => item.family === "IPv4")
      .map((item) => `http://${item.address}:${chalk.green(this.port)}`);
    this.tmpl = readFileSync(path.resolve(__dirname, "./tmpl.html"), "utf8");

    this.mockFile = path.resolve(__dirname, "../mock/index.js");

    this.startCors = true;
    this.startCache = true;
  }
  async processDirectory(res, accessPath, pathname) {
    let dirs = await fs.readdir(accessPath);
    dirs = dirs.map((item) => {
      return {
        href: path.join(pathname, item),
        dir: item,
        size: statSync(path.join(accessPath, item)).size,
      };
    });
    const html = await ejs.render(this.tmpl, { dirs });
    res.setHeader("Content-Type", "text/html;charset=utf-8");
    res.end(html);
  }
  compress(req, res) {
    const acceptEncoding = req.headers["accept-encoding"];
    //  常见的gzip压缩 也可以在客户端来做， 这样可以节约服务端的性能
    if (acceptEncoding) {
      if (acceptEncoding.includes("gzip")) {
        res.setHeader("Content-Encoding", "gzip");
        return zlib.createGzip(); // 转化留   push()
      } else if (acceptEncoding.includes("deflate")) {
        res.setHeader("Content-Encoding", "deflate");
        return zlib.createDeflate();
      } else if (acceptEncoding.includes("br")) {
        res.setHeader("Content-Encoding", "br");
        return zlib.createBrotliDecompress();
      }
    }
  }
  cache(req, res, statObj) {
    // 可以控制浏览器 多久不要在发送请求了， 客户端第一次访问后，添加一个这样的头部
    // 如果采用expires 会有问题，以为这个时间是服务端的设置的时间，客户端发请求的时候 会看这个时间是否过期
    // res.setHeader("Expires", new Date(Date.now() + 10 * 1000).toGMTString());
    // res.setHeader("Cache-Control", "max-age=10"); // 10s 内不会访问服务器

    res.setHeader("Cache-Control", "max-age=0"); // 任何请求都会强制访问服务器（缓存中是有的）
    // no-cache  === max-age=0
    // no-store 不在浏览器上进行缓存

    // 可能这个css文件并没有发生变化，每次重新返回也浪费性能
    // last-modifed

    // 作缓存的原则是减少频率，而不舍尽可能有缓存
    // let ctime = statObj.ctime.toGMTString();
    // res.setHeader("Last-Modified", ctime); // 文件没有变化但是时间变化了， 1s内变化了n次

    // const ifModifiedSince = req.headers["if-modified-since"];
    // if (ifModifiedSince == ctime) {
    //   // 客户端携带的，和服务器的文件的最新的比较
    //   return true;
    // }
    // 1.md5 不可逆 2.不同长度的内容 摘要的结果不一样，3.如果内容变化后结果完全不同 4.相同内容摘要内容相同

    // 最后修改时间 + 文件大小，来做这个指纹
    const etag = statObj.ctime.getTime() + "-" + statObj.size;
    const ifNoneMatch = req.headers["if-none-match"];
    res.setHeader("Etag", etag);
    if (ifNoneMatch == etag) {
      return true;
    }
  }
  async processFile(req, res, accessPath, statObj) {
    // 开是否需要走缓存

    // .txt; text/plain
    // .css; text/css
    if (this.startCache) {
      // 首次访问的页面不能进行强制缓存，默认直接访问的路径会强制取消缓存
      if (this.cache(req, res, statObj)) {
        res.statusCode = 304; // 没有返回内容，去浏览器中的缓存查找
        return res.end();
      }
    }

    // 做文件的压缩处理， 压缩后在返回给服务器, 例如视频音频的 gzip deflate br
    // 可以通过转化流来实现
    res.setHeader(
      "Content-Type",
      (mime.getType(accessPath) || "text/plain") + ";charset=utf-8"
    );
    let transformStream;
    if ((transformStream = this.compress(req, res))) {
      return createReadStream(accessPath).pipe(transformStream).pipe(res);
    }

    createReadStream(accessPath).pipe(res);
  }
  cors(req, res) {
    if (req.headers.origin) {
      // 通过跨域来访问的 ， 此时需要增加跨域头
      res.setHeader("Access-Control-Allow-Origin", req.headers.origin);
      res.setHeader("Access-Control-Allow-Headers", "Content-Type"); // 浏览器可以传递 Access-Control-Allow-Headers
      res.setHeader("Access-Control-Allow-Methods", "DELETE,PUT");
      res.setHeader("Access-Control-Max-Age", 20); // 预检时间的设置
      // 什么时候会有这个options 当我们自定义了header, 简单请求不会增添options
      if (req.method === "OPTIONS") {
        // 实验性请求
        return res.end(); // 可以继续发送其他数据
      }
    }
  }
  async dynamicServer(pathname, req, res) {
    if (pathname.startsWith("/api")) {
      // 接口的路径 是以 /api开头的说明你访问的 是接口
      let accessPath = pathname.replace("/api", "");
      let exits = existsSync(this.mockFile);
      if (exits) {
        let mockFn = require(this.mockFile);
        // 我们需要获取请求体
        req.body = await new Promise((resolve, reject) => {
          const arr = [];
          req.on("data", function (chunk) {
            arr.push(chunk);
          });
          req.on("end", function () {
            let type = req.headers["content-type"];
            if (type === "application/json") {
              let body = Buffer.concat(arr).toString();
              resolve(JSON.parse(body));
            }
            if (type == "application/x-www-form-urlencoded") {
              let body = Buffer.concat(arr).toString();
              resolve(querystring.parse(body, "&", "="));
            } else {
              // ...
              resolve({});
            }
          });
        });
        let returnValue = mockFn(accessPath, req, res);
        if (returnValue) {
          return true;
        }
      }
    }
  }
  requestCallback = async (req, res) => {
    // 先处理动态的 还是静态的？
    //  ssr   前端路由 /about   规定接口的时候 增加 /api/user
    try {
      const { pathname, query } = url.parse(req.url);
      // 常见处理跨域问题的方式  有nginx， websocket来发送请求， cors 服务端解决跨域

      if (this.startCors) {
        let flag = this.cors(req, res);
        if (flag) return; // 试探性请求无需向下执行
      }

      if (await this.dynamicServer(pathname, req, res)) {
        return;
      }

      // favicon.ico
      const accessPath = path.join(this.dir, decodeURIComponent(pathname));
      const statObj = await fs.stat(accessPath);
      if (statObj.isDirectory()) {
        await this.processDirectory(res, accessPath, pathname);
      } else {
        await this.processFile(req, res, accessPath, statObj);
      }
    } catch (e) {
      console.log(e);
      res.statusCode = 404;
      res.end();
    }
  };
  start() {
    const server = http.createServer(this.requestCallback);
    server.listen(this.port, () => {
      this.hosts.forEach((host) => {
        console.log(host);
      });
    });
  }
}
module.exports = Server;
// 处理我们的命令行，
// 包发到npm上
// 处理文件夹的访问
// 实现缓存 ， 跨域的处理
// 文件的压缩。。。。

// express koa
// webpock  mock-server

// 发布npm 包
// 两种一种叫作用域包， 一种就是公开的，没有划分作用域的 （不能喝别的包重名）

// @vue/cli
// @koa/router
// @nestjs/xxx

// 3n  npm  nrm  nvm
// npm login
// npm addUser
// npm whoami
//
